#include <system/ResourceManager.h>
#include <system/Log.h>

namespace ne
{
    uint32_t ResourceManager::RESOURCE_INTERNAL_REFERENCE = 2;

    ResourceManager::ResourceManager()
        : mMemoryUsage(0)
    {
        mMemoryBudget = std::numeric_limits<uint32_t>::max();
    }

    ResourcePtr ResourceManager::create(const std::string &name,
                                        const std::string &fileName,
                                        const bool isManual,
                                        ManualResourceLoader *pLoader)
    {
        if (isManual && !pLoader)
        {
            pLoader = this;
        }

        ResourcePtr pResource(createImplement(getNextID(), name, fileName, isManual, pLoader));
        if (!addImplement(pResource))
        {
            pResource.setNull();
        }
        return pResource;
    }

    ResourceCreateOrRetrieveResult
    ResourceManager::createOrRetrieve(const std::string &name,
                                      const std::string &fileName,
                                      const bool isManual,
                                      ManualResourceLoader *pLoader)
    {
        ResourcePtr pResource(getByName(name));
        bool created = false;
        if (pResource.isNull())
        {
            created = true;
            pResource = create(name, fileName, isManual, pLoader);
        }
        return ResourceCreateOrRetrieveResult(pResource, created);
    }

    ResourcePtr ResourceManager::prepare(const std::string &name,
                                         const std::string &fileName,
                                         const bool isManual,
                                         ManualResourceLoader *pLoader,
                                         const bool backgroundThread)
    {
        ResourcePtr pResource(createOrRetrieve(name, fileName, isManual, pLoader).first);
        if (pResource.notNull())
        {
            pResource->prepare(backgroundThread);
        }
        return pResource;
    }

    ResourcePtr ResourceManager::load(const std::string &name,
                                      const std::string &fileName,
                                      const bool isManual,
                                      ManualResourceLoader *pLoader,
                                      const bool backgroundThread)
    {
        ResourcePtr pResource(createOrRetrieve(name, fileName, isManual, pLoader).first);
        if (pResource.notNull())
        {
            pResource->load(backgroundThread);
        }
        return pResource;
    }

    bool ResourceManager::unloadByID(const uint64_t id)
    {
        ResourcePtr pResource(getByID(id));
        if (pResource.notNull())
        {
            return pResource->unload();
        }
        return false;
    }

    bool ResourceManager::unloadByName(const std::string &name)
    {
        ResourcePtr pResource(getByName(name));
        if (pResource.notNull())
        {
            return pResource->unload();
        }
        return false;
    }

    void ResourceManager::unloadUnreference(const bool reloadableOnly)
    {
        MutexLocker locker(&mMutex);
        for (ResourceMapIterator it=mResourceMap.begin(); it!=mResourceMap.end(); ++it)
        {
            if (it->second.getUseCount() == RESOURCE_INTERNAL_REFERENCE)
            {
                if (!reloadableOnly || it->second->isReloadable())
                {
                    it->second->unload();
                }
            }
        }
    }

    void ResourceManager::unloadAll(const bool reloadableOnly)
    {
        MutexLocker locker(&mMutex);
        for (ResourceMapIterator it=mResourceMap.begin(); it!=mResourceMap.end(); ++it)
        {
            if (!reloadableOnly || it->second->isReloadable())
            {
                it->second->unload();
            }
        }
    }

    bool ResourceManager::reloadByID(const uint64_t id)
    {
        ResourcePtr pResource(getByID(id));
        if (pResource.notNull())
        {
            return pResource->reload();
        }
        return false;
    }

    bool ResourceManager::reloadByName(const std::string &name)
    {
        ResourcePtr pResource(getByName(name));
        if (pResource.notNull())
        {
            return pResource->reload();
        }
        return false;
    }

    void ResourceManager::reloadUnreference(const bool reloadableOnly)
    {
        MutexLocker locker(&mMutex);
        for (ResourceMapIterator it=mResourceMap.begin(); it!=mResourceMap.end(); ++it)
        {
            if (it->second.getUseCount() == RESOURCE_INTERNAL_REFERENCE)
            {
                if (!reloadableOnly || it->second->isReloadable())
                {
                    it->second->reload();
                }
            }
        }
    }

    void ResourceManager::reloadAll(const bool reloadableOnly)
    {
        MutexLocker locker(&mMutex);
        for (ResourceMapIterator it=mResourceMap.begin(); it!=mResourceMap.end(); ++it)
        {
            if (!reloadableOnly || it->second->isReloadable())
            {
                it->second->reload();
            }
        }
    }

    bool ResourceManager::downloadByID(const uint64_t id)
    {
        ResourcePtr pResource(getByID(id));
        if (pResource.notNull())
        {
            return pResource->download();
        }
        return false;
    }

    bool ResourceManager::downloadByName(const std::string &name)
    {
        ResourcePtr pResource(getByName(name));
        if (pResource.notNull())
        {
            return pResource->download();
        }
        return false;
    }

    void ResourceManager::downloadUnreference()
    {
        MutexLocker locker(&mMutex);
        for (ResourceMapIterator it=mResourceMap.begin(); it!=mResourceMap.end(); ++it)
        {
            if (it->second.getUseCount() == RESOURCE_INTERNAL_REFERENCE)
            {
                it->second->download();
            }
        }
    }

    void ResourceManager::downloadAll()
    {
        MutexLocker locker(&mMutex);
        for (ResourceMapIterator it=mResourceMap.begin(); it!=mResourceMap.end(); ++it)
        {
            it->second->download();
        }
    }

    bool ResourceManager::hasID(const uint64_t id) const
    {
        ResourceIDMapConstIterator it = mResourceIDMap.find(id);
        if (it != mResourceIDMap.end())
        {
            return true;
        }
        return false;
    }

    bool ResourceManager::hasName(const std::string &name) const
    {
        ResourceMapConstIterator it = mResourceMap.find(name);
        if (it != mResourceMap.end())
        {
            return true;
        }
        return false;
    }

    ResourcePtr ResourceManager::getByID(const uint64_t id) const
    {
        ResourceIDMapConstIterator it = mResourceIDMap.find(id);
        if (it != mResourceIDMap.end())
        {
            return it->second;
        }
        return ResourcePtr();
    }

    ResourcePtr ResourceManager::getByName(const std::string &name) const
    {
        ResourceMapConstIterator it = mResourceMap.find(name);
        if (it != mResourceMap.end())
        {
            return it->second;
        }
        return ResourcePtr();
    }

    bool ResourceManager::removeByPtr(const ResourcePtr &pResource)
    {
        return removeImplement(pResource);
    }

    bool ResourceManager::removeByID(const uint64_t id)
    {
        ResourcePtr pResource(getByID(id));
        if (pResource.notNull())
        {
            return removeImplement(pResource);
        }
        return false;
    }

    bool ResourceManager::removeByName(const std::string &name)
    {
        ResourcePtr pResource(getByName(name));
        if (pResource.notNull())
        {
            return removeImplement(pResource);
        }
        return false;
    }

    void ResourceManager::removeUnreference(const bool reloadableOnly)
    {
        MutexLocker locker(&mMutex);
        std::list<ResourcePtr> resourcesToDestroy;
        for (ResourceMapIterator it=mResourceMap.begin(); it!=mResourceMap.end(); ++it)
        {
            if (it->second.getUseCount() == RESOURCE_INTERNAL_REFERENCE)
            {
                if (!reloadableOnly || it->second->isReloadable())
                {
                    resourcesToDestroy.push_back(it->second);
                }
            }
        }

        for (std::list<ResourcePtr>::iterator i=resourcesToDestroy.begin(); i!=resourcesToDestroy.end(); ++i)
        {
            ResourceMapIterator it = mResourceMap.find((*i)->getName());
            if (it != mResourceMap.end())
            {
                mResourceMap.erase(it);
            }

            ResourceIDMapIterator itID = mResourceIDMap.find((*i)->getID());
            if (itID != mResourceIDMap.end())
            {
                mResourceIDMap.erase(itID);
            }

            if ((*i).notNull())
            {
                (*i)->mpManager = 0;
            }
        }
    }

    void ResourceManager::removeAll()
    {
        MutexLocker locker(&mMutex);
        for (ResourceMapIterator it=mResourceMap.begin(); it!=mResourceMap.end(); ++it)
        {
            if (it->second.notNull())
            {
                it->second->mpManager = 0;
            }
        }
        mResourceMap.clear();
        mResourceIDMap.clear();
    }

    void ResourceManager::onLoadingComplete(Resource *pResource)
    {
        if (pResource)
        {
            mMemoryUsage += pResource->getSize();
        }
    }

    void ResourceManager::onUnloadingComplete(Resource *pResource)
    {
        if (pResource)
        {
            mMemoryUsage -= pResource->getSize();
        }
    }

    ResourceManager::~ResourceManager()
    {
        removeAll();
    }

    uint64_t ResourceManager::getNextID()
    {
        return mNextID++;
    }

    bool ResourceManager::addImplement(const ResourcePtr &pResource)
    {
        if (pResource.isNull()) return false;

        if (hasName(pResource->getName()))
        {
            Log::Out(LL_WARNING,
                "Resource [%s] : (%s) is exist", pResource->type(), pResource->getName().c_str());
            return false;
        }

        MutexLocker locker(&mMutex);
        mResourceMap[pResource->getName()] = pResource;
        mResourceIDMap[pResource->getID()] = pResource;

        return true;
    }

    bool ResourceManager::removeImplement(const ResourcePtr &pResource)
    {
        if (pResource.isNull()) return false;

        MutexLocker locker(&mMutex);
        ResourceMapIterator it = mResourceMap.find(pResource->getName());
        if (it != mResourceMap.end())
        {
            mResourceMap.erase(it);
        }

        ResourceIDMapIterator itID = mResourceIDMap.find(pResource->getID());
        if (itID != mResourceIDMap.end())
        {
            mResourceIDMap.erase(itID);
        }

        pResource->mpManager = 0;

        return true;
    }

    void ResourceManager::checkMemoryUsage()
    {
    }
}
